package eu.darken.octi.sync.module.syncs.jserver.core

import com.gitee.wsl.exception.asLog
import com.gitee.wsl.exception.logTag
import com.gitee.wsl.flow.setupCommonEventHandlers
import com.gitee.wsl.flow.shareLatest
import eu.darken.octi.sync.core.ConnectorHub
import eu.darken.octi.sync.core.ConnectorId
import eu.darken.octi.sync.core.SyncConnector
import eu.darken.octi.sync.core.SyncSettings
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import timber.log.Timber


class JServerHub  constructor(
    private val scope: CoroutineScope,
    dispatcherProvider: CoroutineDispatcher=Dispatchers.Default,
    private val accountRepo: JServerAccountRepo,
    private val connectorFactory: JServerConnector.Factory,
    private val endpointFactory: JServerEndpoint.Factory,
    private val syncSettings: SyncSettings,
) : ConnectorHub {

    @OptIn(ExperimentalCoroutinesApi::class)
    private val _connectors = accountRepo.accounts
        .mapLatest { acc ->
            acc.map { connectorFactory.create(it) }
        }
        .setupCommonEventHandlers(TAG) { "connectors" }
        .shareLatest(scope + dispatcherProvider)

    override val connectors: Flow<Collection<SyncConnector>> = _connectors

    override suspend fun owns(connectorId: ConnectorId): Boolean {
        return _connectors.first().any { it.identifier == connectorId }
    }

    override suspend fun remove(connectorId: ConnectorId) = withContext(NonCancellable) {
        Timber.d(  "remove(id=$connectorId)" )
        val connector = _connectors.first().single { it.identifier == connectorId }
        try {
            connector.deleteDevice(syncSettings.deviceId)
        } catch (e: Exception) {
            Timber.e( "Failed to delete ourselves from $connectorId: ${e.asLog()}" )
        }
        accountRepo.remove(connector.credentials.accountId)
    }

    suspend fun linkAcount(linkingData: LinkingData) = withContext(NonCancellable) {
        Timber.d( "linkAccount(link=$linkingData)" )

        val endPoint = endpointFactory.create(linkingData.serverAdress)

        val linkedAccount = endPoint.linkToExistingAccount(
            linkingData.linkCode,
        )

        val newCredentials = JServer.Credentials(
            serverAdress = linkingData.serverAdress,
            accountId = linkedAccount.accountId,
            devicePassword = linkedAccount.devicePassword,
            encryptionKeyset = linkingData.encryptionKeyset,
        )

        Timber.d( "Account successfully linked: $newCredentials" )

        accountRepo.add(newCredentials)
    }

    companion object {
        private val TAG = logTag("Sync", "JServer", "Hub")
    }
}